#include <iostream>
#include <functional>
#include <string>
#include <vector>

#include <cstdlib>
#include <cassert>

#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>

#define PROCESS_NUM 5

// 函数指针
typedef void (*func_t)();

// 进程的方法
void add()
{
    std::cout << "id为" << getpid() << "正在执行加法任务" << std::endl;
    sleep(1); // 模拟该任务花费的时间
}

void sub()
{
    std::cout << "id为" << getpid() << "正在执行减法任务" << std::endl;
    sleep(1);
}

void print()
{
    std::cout << "id为" << getpid() << "正在执行打印任务" << std::endl;
    sleep(1); // 模拟该任务花费的时间
}

void download()
{
    std::cout << "id为" << getpid() << "正在执行下载任务" << std::endl;
    sleep(1);
}

// 建立一个任务集合表  func_t
// 此处既可以使用函数指针，也可以使用C++自带的function进行包装
void loadTaskFunc(std::vector<func_t> *funcMap)
{
    assert(funcMap != nullptr);
    funcMap->push_back(add);
    funcMap->push_back(sub);
    funcMap->push_back(print);
    funcMap->push_back(download);
}

//--------------------------------------------------------------------

class SubEp
{
public:
    SubEp(int writeFd, pid_t pid)
        : _writeFd(writeFd), _pid(pid)
    {
        char buffer[64];
        snprintf(buffer, sizeof(buffer), "process: num[%d]pid[%d]fd[%d]", num++, pid, writeFd);
        _name = buffer;
    }

public:
    int _writeFd;      // 该进程的管道文件描述符
    pid_t _pid;        // 该进程的pid
    std::string _name; // 该进程的名称
    static int num;    // 这是第几个进程
};
int SubEp::num = 1;

// 子进程接收来自父进程从管道传递过来的信息码
int receiveTask(int readFd)
{
    int code = 0;
    ssize_t n = read(readFd, &code, sizeof(code));
    if (n == 4) // 传递4个字节说明是一个整数
        return code;
    return -1;
}

// 父进程将一个随机信息码发送给一个随机的子进程
void sendTask(const SubEp &process, const int &num)
{
    std::cout << "send task num: " << num << " to ->" << process._name << std::endl;
    int n = write(process._writeFd, &num, sizeof(num));
    assert(n == sizeof(num));
    (void)n;
}

// 参数分别是子进程集合，方法集合
void creatSubProcess(std::vector<SubEp> &subs, std::vector<func_t> &funcMap)
{
    for (int i = 0; i < PROCESS_NUM; i++)
    {
        int fds[2];
        int n = pipe(fds); // 创建管道文件
        assert(n == 0);

        pid_t id = fork();
        if (id == 0)
        {
            // 子进程，需要关闭写端
            close(fds[1]);

            // 接收来自父进程的信息码并处理任务，如果没有则阻塞等待
            while (true)
            {
                int commandCode = receiveTask(fds[0]);
                if (commandCode >= 0 && commandCode <= funcMap.size())
                    funcMap[commandCode]();
                else if (commandCode == -1)
                    break;
            }
            exit(0);
        }

        // 父进程，需要将子进程和其管道信息保存好到subs中
        close(fds[0]);
        SubEp sub(fds[1], id);
        subs.push_back(sub);
    }
}

void loadBalanceContrl(const std::vector<SubEp> &subs, const std::vector<func_t> &funcMap, int count)
{
    int subProcessNum = subs.size();
    int funcNum = funcMap.size();

    bool flag = (count == 0 ? true : false);
    while (true)
    {
        // 1.随机选择一个子进程
        int subIndex = rand() % subProcessNum;

        // 2.随机选择一个任务码
        int taskcode = rand() % funcNum;

        // 3.将任务码发送相应的子进程进行执行任务
        sendTask(subs[subIndex], taskcode);

        if (!flag) // 表明任务还没有结束
        {
            count--;
            if (count == 0) // 任务数量为0，结束
                break;
        }

        sleep(1);
    }

    // 依次关闭子进程的管道文件描述符
    for (int i = 0; i < subs.size(); i++)
    {
        close(subs[i]._writeFd);
    }
}

void waitSubProcess(const std::vector<SubEp> &subs)
{
    int processNum = subs.size();
    for (int i = 0; i < subs.size(); i++)
    {
        int status = 0;
        waitpid(subs[i]._pid, &status, 0);
        std::cout << "子进程id:" << subs[i]._pid << " 该进程的退出码为:" << ((status >> 8) & 0xff) << " 该进程的退出信号是:" << (status & 0x7f) << std::endl;
    }
}

int main()
{
    srand((unsigned int)time(0));
    // 1.创建方法表
    std::vector<func_t> funcMap; // 方法表集合
    loadTaskFunc(&funcMap);

    // 创建子进程并维护好管道文件
    std::vector<SubEp> subs; // 子进程封装
    creatSubProcess(subs, funcMap);

    // 负载均衡派发任务
    int taskCount = 15;
    loadBalanceContrl(subs, funcMap, taskCount);

    // 等待子进程
    waitSubProcess(subs);

    return 0;
}
